﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MacomberMapSystem.Common.Serialization;
using System.Windows.Forms;
using MacomberMapSystem.Common.Integration;
using System.IO;
using MacomberMapSystem.Common.Database;
using System.Data.Common;
using System.Drawing;
using MacomberMapSystem.Common.Exports;
using MacomberMapSystem.Common.CIM;
using System.Xml;
using System.Data.OleDb;
using MacomberMapSystem.Common.Internals;
using System.ComponentModel;
using MacomberMapSystem.Common.Processing.Attributes;
using MacomberMapSystem.Common.User_Interfaces.One_Lines;
#if DotNetOracle
using System.Data.OracleClient;
#else
    using Oracle.DataAccess.Client;
#endif

namespace MacomberMapSystem.Common.Processing
{
    /// <summary>
    /// (C) 2013, Michael E. Legatt, Ph.D., Electric Reliability Council of Texas, Inc.
    /// This class provides all members for model maintenance
    /// </summary>
    public static class CIM_Model_Maintenance
    {

        /// <summary>
        /// Re-index our model identifiers
        /// </summary>
        /// <param name="Repository"></param>
        public static void ReIndexModels(MM_Repository Repository)
        {
            //Build our mapping of models
            Dictionary<int, int> ModelMapping = new Dictionary<int, int>();
            Dictionary<String, int[]> MM_Database_Model = new Dictionary<string, int[]>();
            using (OracleCommand cmd = Repository.Db.CreateCommand("SELECT ROWID,ID FROM MM_DATABASE_MODEL ORDER BY ID") as OracleCommand)
            using (OracleDataReader oRd = cmd.ExecuteReader())
                while (oRd.Read())
                {
                    ModelMapping.Add(Convert.ToInt32(oRd["ID"]), ModelMapping.Count + 1);
                    MM_Database_Model.Add(oRd["ROWID"].ToString(), new int[] { ModelMapping[Convert.ToInt32(oRd["ID"])], Convert.ToInt32(oRd["ID"]) });
                }

            //Now, go through our tables and adjust as appropirate
            Dictionary<String, int[]> MM_Database_Assignment = new Dictionary<string, int[]>();
            using (OracleCommand cmd = Repository.Db.CreateCommand("SELECT ROWID,MODEL FROM MM_Database_Assignment") as OracleCommand)
            using (OracleDataReader oRd = cmd.ExecuteReader())
                while (oRd.Read())
                    MM_Database_Assignment.Add(oRd["ROWID"].ToString(), new int[] { ModelMapping[Convert.ToInt32(oRd["Model"])], Convert.ToInt32(oRd["Model"]) });

            int FoundTarget;
            Dictionary<String, String[]> MM_Database_OneLine = new Dictionary<String, String[]>();
            using (OracleCommand cmd = Repository.Db.CreateCommand("SELECT ROWID,ID, MODELS FROM MM_Database_OneLine") as OracleCommand)
            using (OracleDataReader oRd = cmd.ExecuteReader())
                while (oRd.Read())
                {
                    String[] splStr = oRd["Models"].ToString().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                    for (int a = 0; a < splStr.Length; a++)
                        if (ModelMapping.TryGetValue(Convert.ToInt32(splStr[a]), out FoundTarget))
                            splStr[a] = FoundTarget.ToString();
                        else
                            Console.WriteLine("Oneline " + oRd["ID"].ToString() + " references model " + splStr[a] + ", which could not be found");
                    MM_Database_OneLine.Add(oRd["ROWID"].ToString(), new string[] { "," + String.Join(",", splStr) + ",", oRd["Models"].ToString() });
                }


            //With user permission, update
            if (MessageBox.Show("Are you sure you want to update the model database collection?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                using (OracleTransaction oTrans = Repository.Db.Database.BeginTransaction() as OracleTransaction)
                {
                    foreach (KeyValuePair<String, int[]> kvp in MM_Database_Model)
                        using (OracleCommand oCmd = Repository.Db.CreateCommand("UPDATE MM_DATABASE_MODEL SET ID=:0, OLDID=:1 WHERE ROWID=:2", kvp.Value[0], kvp.Value[1], kvp.Key) as OracleCommand)
                        {
                            oCmd.Transaction = oTrans;
                            oCmd.ExecuteNonQuery();
                        }
                    foreach (KeyValuePair<String, int[]> kvp in MM_Database_Assignment)
                        using (OracleCommand oCmd = Repository.Db.CreateCommand("UPDATE MM_DATABASE_ASSIGNMENT SET MODEL=:0, OLDMODEL=:1 WHERE ROWID=:2", kvp.Value[0], kvp.Value[1], kvp.Key) as OracleCommand)
                        {
                            oCmd.Transaction = oTrans;
                            oCmd.ExecuteNonQuery();
                        }
                    foreach (KeyValuePair<String, string[]> kvp in MM_Database_OneLine)
                        using (OracleCommand oCmd = Repository.Db.CreateCommand("UPDATE MM_DATABASE_ONELINE SET MODELS=:0, OLDMODELS=:1 WHERE ROWID=:2", kvp.Value[0], kvp.Value[1], kvp.Key) as OracleCommand)
                        {
                            oCmd.Transaction = oTrans;
                            oCmd.ExecuteNonQuery();
                        }

                    if (MessageBox.Show("Would you like to commit the changes?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                        oTrans.Commit();
                    else
                        oTrans.Rollback();
                }
        }

        /// <summary>
        /// Load district files from a directory
        /// </summary>
        /// <param name="Directory"></param>
        public static MM_District[] LoadDistrictFiles(String Directory)
        {
            MM_District[] Districts = MM_District.ExtractDistricts(new DirectoryInfo(Directory));
            return Districts;
        }

        /// <summary>
        /// Export the collection of OTS displays for substations with synchroscopes
        /// </summary>
        /// <param name="Directory"></param>
        /// <param name="Repository"></param>
        /// <param name="CurModel"></param>
        public static void ExportOTSSynchroscopeStations(String Directory, MM_Repository Repository, MM_Database_Model CurModel)
        {
            Dictionary<CIM_RdfID, bool> Synchroscopes = Repository.Db.Synchroscopes;
            foreach (CIM_Element Sub in CurModel.Substations.Values)
                if (bool.Parse(Sub["etx:Substation.forNetwork"]) && bool.Parse(Sub["etx:Substation.forScada"]))
                {
                    bool Export = false;
                    foreach (CIM_Element Elem in Sub.Elements)
                        if (!Export && Elem.TypeName == "cim:Breaker")
                            if (Synchroscopes.ContainsKey(Sub.rdfID) || Synchroscopes.ContainsKey(Elem.rdfID) || Synchroscopes.ContainsKey(Elem.Operator.rdfID))
                                Export = true;
                    if (Export)
                    {
                        int OneLineID;
                        using (DbCommand dCmd = Repository.Db.CreateCommand("SELECT ID FROM MM_DATABASE_ONELINE WHERE RDFID=:0 AND MODELS LIKE :1", Sub.rdfID.ToString(), "%," + CurModel.ID.ToString() + ",%"))
                            OneLineID = Convert.ToInt32(dCmd.ExecuteScalar());
                        MM_Database_OneLine OneLine = Repository.Db.LoadOneLine(OneLineID, Repository);
                        Image picImage = Repository.Db.LoadOneLineImage(OneLineID);
                        MM_DDL_Exporter.ExportOneLine(OneLine, CurModel, Path.Combine(Directory, "OTS-" + Sub.Name + "_dset.ddl"), false, true, picImage, CurModel.LocateExport(OneLine), null, Repository.Db.GetLastAssignment(OneLine));
                        Application.DoEvents();
                    }
                }
        }

        //TODO: This may be duplicated - double-check
        /// <summary>
        /// Write out an equipment operatorship file for a model
        /// </summary>
        /// <param name="Model"></param>
        public static void WriteEquipmentOperatorship(MM_Database_Model Model)
        {
            using (StreamWriter sW = new StreamWriter(Path.Combine(Model.ModelFullPath, Model.Name + "-EquipOperatorship.csv"), false, Encoding.ASCII))
            using (StreamWriter sW2 = new StreamWriter(Path.Combine(Model.ModelFullPath, Model.Name + "-StOperatorship.csv"), false, Encoding.ASCII))
            {

                foreach (CIM_Element Sub in Model.Substations.Values)
                    if (bool.Parse(Sub["etx:Substation.forNetwork"]) && bool.Parse(Sub["etx:Substation.forScada"]))
                    {
                        Dictionary<CIM_Element, String> Companies = new Dictionary<CIM_Element, string>();
                        foreach (CIM_Element Elem in Sub.Elements)
                            if (Elem.Operator != null)
                            {
                                String Alias = Elem.Operator.Attributes["cim:IdentifiedObject.aliasName"];
                                if (!Companies.ContainsKey(Elem.Operator))
                                    Companies.Add(Elem.Operator, Alias);
                                if (Elem.TypeName == "cim:ACLineSegment")
                                    sW.WriteLine("{0},{1},{2},{3}", Elem.ElemType, Sub.Name.ToUpper(), Elem.LineName.ToUpper(), Alias);
                                else if (Elem.TypeName == "cim:SeriesCompensator")
                                    sW.WriteLine("{0},{1},{2},{3}", "SeriesCompensator", Elem.ACSegmentStations[0].Name, Elem.LineName.ToUpper(), Alias);
                                else if (Elem.TypeName == "cim:StaticVarCompensator" || Elem.TypeName == "cim:PowerTransformer" || Elem.TypeName == "cim:Disconnector" || Elem.TypeName == "cim:Breaker" || Elem.TypeName == "cim:SeriesCompensator" || Elem.TypeName == "cim:ShuntCompensator" || Elem.TypeName == "cim:TransformerWinding")
                                    sW.WriteLine("{0},{1},{2},{3}", Elem.ElemType, Elem.Substation.Name, Elem.Name, Alias);
                                else if (Elem.TypeName == "etx:FrequencyRelay" || Elem.TypeName == "cim:GroundDisconnector" || Elem.TypeName == "cim:StationSupply" || Elem.TypeName == "etx:PricingVector" || Elem.TypeName == "etx:VoltageRelay" || Elem.TypeName == "cim:SynchrocheckRelay" || Elem.TypeName == "cim:Bay" || Elem.TypeName == "etx:EndCap" || Elem.TypeName == "etx:VoltageMonitored" || Elem.TypeName == "etx:ConnectivityNodeGroup" || Elem.TypeName == "cim:ConnectivityNode")
                                { }
                                else if (Elem.TypeName == "cim:BusbarSection")
                                {
                                    CIM_Element Node = Elem.ParseSingle("cim:Terminal>cim:ConnectivityNode", Model);
                                    sW.WriteLine("{0},{1},{2},{3}", Elem.ElemType, Elem.Substation.Name, Node.Name, Alias);
                                }
                                else if (Elem.TypeName == "cim:SynchronousMachine")
                                {
                                    sW.WriteLine("{0},{1},{2},{3}", Elem.ElemType, Elem.Substation.Name, Elem.Name, Alias);
                                    foreach (CIM_Element OwnerShareRating in Elem.Parse("Unit<OwnerShareLimits", Model))
                                        sW.WriteLine("{0},{1},{2},{3}", OwnerShareRating.ElemType, Elem.Substation.Name, OwnerShareRating.Name.StartsWith(Elem.Substation.Name, StringComparison.CurrentCultureIgnoreCase) ? OwnerShareRating.Name.Substring(Elem.Substation.Name.Length + 1) : OwnerShareRating.Name, OwnerShareRating.Operator.Attributes["cim:IdentifiedObject.aliasName"]);
                                }
                                else if (Elem.TypeName.EndsWith("Load"))
                                {
                                    sW.WriteLine("{0},{1},{2},{3}", Elem.ElemType, Elem.Substation.Name, Elem.Name, Alias);
                                    foreach (CIM_Element LoadResource in Elem.Parse("LoadResource", Model))
                                        sW.WriteLine("{0},{1},{2},{3}", LoadResource.ElemType, Elem.Substation.Name, LoadResource.Name, Alias);
                                }
                            }
                        sW2.WriteLine(Sub.Name + "," + String.Join(",", Companies.Values.ToArray()));
                        sW.Flush();
                        sW2.Flush();
                    }

                //Write out our combined cycle plant information
                foreach (CIM_Element Elem in Model.ElementsOfType("cim:CombinedCyclePlant"))
                    sW.WriteLine("{0},{1},{2},{3}", Elem.ElemType, Elem.Substation.Name, Elem.Name.StartsWith(Elem.Substation.Name, StringComparison.CurrentCultureIgnoreCase) ? Elem.Name.Substring(Elem.Substation.Name.Length + 1) : Elem.Name, Elem.Operator.Attributes["cim:IdentifiedObject.aliasName"]);
            }
        }

        /// <summary>
        /// Re-validate substations that have jumpers
        /// </summary>
        /// <param name="Repository"></param>
        /// <param name="CurModel"></param>
        public static void RevalidateSubstationsWithJumpers(MM_Repository Repository, MM_Database_Model CurModel)
        {
            StringBuilder sB = new StringBuilder();
            int Warnings = 0;
            using (DbCommand dCmd = Repository.Db.CreateCommand("SELECT RDFID, DOCUMENT FROM MM_DATABASE_ONELINE WHERE MODELS LIKE '%," + CurModel.ID.ToString() + ",%' AND DOCUMENT LIKE '%IsJumper=\"true\"%'"))
            using (DbDataReader dRd = dCmd.ExecuteReader())
                while (dRd.Read())
                {
                    XmlDocument xDoc = new XmlDocument();
                    xDoc.LoadXml((string)dRd["DOCUMENT"]);
                    foreach (XmlNode xPoke in xDoc.SelectNodes("//PokePoint[@IsJumper='true']"))
                    {

                        if (xPoke.Attributes["Orientation"].Value == "Horizontal" && !xPoke.Attributes["Bounds"].Value.TrimEnd(',').EndsWith(",35,11"))
                            sB.AppendLine(String.Format("WRN {0:000}: Connectivity Node {1} to {2} with TEID {3:#,##0} ({4}) {5} jumper at {6} isn't sized properly.", ++Warnings, xPoke.ParentNode.ParentNode.Attributes["BaseElement.Name"].Value, xPoke.ParentNode.Name, xPoke.ParentNode.Attributes["TEID"].Value, xPoke.ParentNode.ParentNode.Attributes["BaseElement.KVLevel"].Value, xPoke.Attributes["Orientation"].Value, xPoke.Attributes["Bounds"].Value));
                        else if (xPoke.Attributes["Orientation"].Value == "Vertical" && !xPoke.Attributes["Bounds"].Value.TrimEnd(',').EndsWith(",11,35"))
                            sB.AppendLine(String.Format("WRN {0:000}: Connectivity Node {1} to {2} with TEID {3:#,##0} ({4}) {5} jumper at {6} isn't sized properly.", ++Warnings, xPoke.ParentNode.ParentNode.Attributes["BaseElement.Name"].Value, xPoke.ParentNode.Name, xPoke.ParentNode.Attributes["TEID"].Value, xPoke.ParentNode.ParentNode.Attributes["BaseElement.KVLevel"].Value, xPoke.Attributes["Orientation"].Value, xPoke.Attributes["Bounds"].Value));
                    }
                }
        }


        /// <summary>
        /// Recompute substation's county memberships, and update if requested
        /// </summary>
        /// <param name="Repository"></param>
        public static void RecomputeCountyMembership(MM_Repository Repository)
        {
            Dictionary<MM_Substation, MM_Boundary[]> ChangedBoundaries = new Dictionary<MM_Substation, MM_Boundary[]>();
            foreach (MM_Substation Sub in Repository.Db.Substations.Values)
            {
                MM_Boundary OldBound = Sub.Boundary;
                Sub.FindBoundary(Repository);
                if (!Sub.Boundary.Equals(OldBound))
                    ChangedBoundaries.Add(Sub, new MM_Boundary[] { Sub.Boundary, OldBound });
            }

            if (MessageBox.Show("There are " + ChangedBoundaries.Count.ToString("#,##0") + " substations with updated boundaries. Push the changes to the database?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
            {
                StringBuilder sB = new StringBuilder();
                foreach (KeyValuePair<MM_Substation, MM_Boundary[]> kvp in ChangedBoundaries)
                    sB.AppendLine("Update MM_Substation SET BOUNDARY=" + kvp.Key.Boundary.ID.ToString() + " WHERE ID=" + kvp.Key.ID.ToString() + ";");
                Clipboard.SetText(sB.ToString());
                if (MessageBox.Show("SQL updates copied to clipboard. Execute as well?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                    using (DbCommand dCmd = Repository.Db.CreateCommand(sB.ToString()))
                        MessageBox.Show("Updated " + dCmd.ExecuteNonQuery().ToString() + " rows.", Application.ProductName);
            }
        }

        /// <summary>
        /// Load substation coordinates from our 
        /// </summary>
        /// <param name="Repository"></param>
        /// <param name="FileName"></param>
        /// <param name="Model"></param>
        public static void LoadSubstationCoordinates(MM_Repository Repository, String FileName, MM_Database_Model Model)
        {
            //Start by clearing out our collection of substation coordinates
            foreach (MM_Substation Sub in Repository.Db.Substations.Values)
            {
                Sub.LatLong = PointD.Empty;
                Sub.Boundary = Repository.Db.StateBoundary;
                Sub.UpdateRdb(Repository);
            }

            //Open up our file
            StreamWriter sW = new StreamWriter(Path.Combine(Path.GetDirectoryName(FileName), Path.GetFileNameWithoutExtension(FileName) + ".log"), false, new UTF8Encoding(false));            
            sW.WriteLine("Coordinates load log, started " + DateTime.Now.ToString());
            int Skipped = 0, Processed = 0;
            DateTime StartTime = DateTime.Now;
            using (OleDbConnection oConn = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=\"" + FileName + "\";Extended Properties=\"Excel 12.0 Macro;HDR=YES\";"))
            {
                oConn.Open();
                using (OleDbCommand oCmd = new OleDbCommand("SELECT * FROM [Model-Subs$]", oConn))
                using (OleDbDataReader oRd = oCmd.ExecuteReader())
                    while (oRd.Read())
                    {
                        MM_Substation FoundSub;
                        if (!Repository.Db.Substations.TryGetValue(new CIM_RdfID(oRd["rdf:ID"].ToString()), out FoundSub))
                            sW.WriteLine("Unable to location substation with rdf:ID " + oRd["rdf:ID"].ToString() + " and name " + oRd["Name"].ToString() + " / " + oRd["Long Name"].ToString());
                        else if (oRd["Latitude"] is Double)
                        {
                            FoundSub.LatLong = new PointD(Convert.ToDouble(oRd["Longitude"]), Convert.ToDouble(oRd["Latitude"]));
                            FoundSub.FindBoundary(Repository);
                            FoundSub.UpdateRdb(Repository);
                            WriteMessage(sW, "Updated {0} ({1}) to {2}, {3} (in county {4})", FoundSub.Name, FoundSub.Longitude, FoundSub.LatLong.Y, FoundSub.LatLong.X, FoundSub.Boundary.Name);                            
                            Processed++;
                        }
                        else
                        {
                            WriteMessage(sW, "Ignoring N/A coordinates for {0} ({1})", oRd["Name"], oRd["Long Name"]);
                            Skipped++;
                        }
                        Application.DoEvents();
                    }
            }
            sW.WriteLine();
            WriteMessage(sW, "Coordinate process completed. {0:#,##0} substations processed, {1:#,##0} substations skipped. Total time: {2}", Processed, Skipped, DateTime.Now - StartTime);            

            //Now, update our coordinates based on our model
            CIM_Element FoundCIMSub = null;
            Dictionary<MM_Substation, CIM_Element> SubstationsNeedingReposition = new Dictionary<MM_Substation, CIM_Element>();
            foreach (MM_Substation Sub in Repository.Db.Substations.Values)
                if (Sub.LatLong.IsEmpty && (FoundCIMSub = Model.FromRdfID(Sub.rdfID)) != null)
                    SubstationsNeedingReposition.Add(Sub, FoundCIMSub);

            //Now, handle all substation that need repositioning get updated.
            for (int Iteration = 0; Iteration < 5; Iteration++)
                foreach (KeyValuePair<MM_Substation, CIM_Element> Sub in new Dictionary<MM_Substation, CIM_Element>(SubstationsNeedingReposition))
                    if (CIM_Model_Maintenance.UpdateCoordinates(Sub.Key, Sub.Value, Iteration, sW, Repository))
                        SubstationsNeedingReposition.Remove(Sub.Key);

            //Now, write out a model file for our substations and lines
            WriteKMLFile(Path.Combine(Path.GetDirectoryName(FileName), Path.GetFileNameWithoutExtension(FileName) + ".kml"), Model, Repository);
            WriteSubstationAndLineCoordinates(Path.Combine(Path.GetDirectoryName(FileName), Path.GetFileNameWithoutExtension(FileName) + "-Exported.xls"), Model, Repository);
            sW.Close();
            sW.Dispose();
            MessageBox.Show("Update complete.");
        }

        /// <summary>
        /// Write out a KML file
        /// </summary>
        /// <param name="FileName"></param>
        /// <param name="Model"></param>
        /// <param name="Repository"></param>
        public static void WriteKMLFile(String FileName, MM_Database_Model Model, MM_Repository Repository)
        {
            //Build our list of voltages
            Dictionary<String, String> KVLevels = new Dictionary<string, string>();
            KVLevels.Add("345 KV", "ff00ff00");
            KVLevels.Add("138 KV", "ffff0000");
            KVLevels.Add("69 KV",  "ffff00ff");
            KVLevels.Add("DC Tie", "ffffffff");



            using (StreamWriter sW = new StreamWriter(FileName, false, new UTF8Encoding(false)))
            {
                sW.WriteLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
                sW.WriteLine("<kml xmlns=\"http://www.opengis.net/kml/2.2\" xmlns:gx=\"http://www.google.com/kml/ext/2.2\" xmlns:kml=\"http://www.opengis.net/kml/2.2\" xmlns:atom=\"http://www.w3.org/2005/Atom\">");
                sW.WriteLine("<Document>");
                sW.WriteLine("<name>Coordinate Matchup</name>");

                //Write our pushpin and colors
                sW.WriteLine("<Style id=\"sn_grn-pushpin10000\">");
                sW.WriteLine("<IconStyle>");
                sW.WriteLine("<scale>1.1</scale>");
                sW.WriteLine("<Icon>");
                sW.WriteLine("<href>http://maps.google.com/mapfiles/kml/pushpin/grn-pushpin.png</href>");
                sW.WriteLine("</Icon>");
                sW.WriteLine("<hotSpot x=\"20\" y=\"2\" xunits=\"pixels\" yunits=\"pixels\"/>");
                sW.WriteLine("</IconStyle>");
                sW.WriteLine("<BalloonStyle>");
                sW.WriteLine("<text><![CDATA[<p align=\"left\" style=\"white-space:nowrap;\"><font size=\"+1\"><b>$[name]</b></font></p> <p align=\"left\">$[description]</p>]]></text>");
                sW.WriteLine("</BalloonStyle>");
                sW.WriteLine("</Style>");

                //Write out our voltage levels
                foreach (KeyValuePair<string, string> kvp in KVLevels)
                {
                    sW.WriteLine("<Style id=\"" + kvp.Key.Replace(' ','_') + "\">");
                    sW.WriteLine("<IconStyle>");
                    sW.WriteLine("<scale>1.3</scale>");
                    sW.WriteLine("<Icon>");
                    sW.WriteLine("<href>http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png</href>");
                    sW.WriteLine("</Icon>");
                    sW.WriteLine("<hotSpot x=\"20\" y=\"2\" xunits=\"pixels\" yunits=\"pixels\"/>");
                    sW.WriteLine("</IconStyle>");
                    sW.WriteLine("<LineStyle>");
                    sW.WriteLine("<color>" + kvp.Value + "</color>");
                    sW.WriteLine("<width>2</width>");
                    sW.WriteLine("</LineStyle>");
                    sW.WriteLine("</Style>");
                }


                //Write out our substations
                MM_Substation FoundSub1, FoundSub2;
                sW.WriteLine("<Folder id=\"Waypoints\">");
                sW.WriteLine("<name>Substations</name>");
                sW.WriteLine("<visibility>1</visibility>");
                foreach (CIM_Element Sub in Model.Substations.Values)
                    if (bool.Parse(Sub["etx:Substation.forNetwork"]) && bool.Parse(Sub["etx:Substation.forScada"]) && Repository.Db.Substations.TryGetValue(Sub.rdfID, out FoundSub1))
                    {
                        sW.WriteLine("<Placemark>");
                        sW.WriteLine("<name>" + MM_Type_Converter.TitleCase(Sub.LongName) + "</name>");
                        sW.WriteLine("<Snippet maxLines=\"0\"></Snippet>");
                        sW.WriteLine("<description>");
                        sW.WriteLine("EMS Name: " + MM_Type_Converter.TitleCase(Sub.Name) + "<br/>");
                        sW.WriteLine("TEID:" + Sub.TEID + "<br/>");
                        sW.WriteLine("rdf:ID: " + Sub.rdfID.ToString() + "<br/>");
                        sW.WriteLine("Operator:" + MM_Type_Converter.TitleCase(Sub.Operator.Name) + "<br/>");
                        sW.WriteLine("Connected substations: " + Sub.ConnectedStationLongNames + "<br/>");
                        sW.WriteLine("Connected lines: " + Sub.ConnectedLineLongNames + "<br/>");
                        sW.WriteLine("Voltages: " + Sub.VoltageLevels + "<br/>");
                        sW.WriteLine("PSSE Bus Numbers: " + Sub.PSSEBusNumbers + "<br/>");
                        sW.WriteLine("PSSE Bus Names: " + Sub.PSSEBusNames + "<br/>");
                        sW.WriteLine("</description>");
                        sW.WriteLine("<styleUrl>#sn_grn-pushpin10000</styleUrl>");
                        sW.WriteLine("<Point>");
                        sW.WriteLine("<coordinates>" + FoundSub1.Longitude.ToString() + "," + FoundSub1.Latitude.ToString() + ",0</coordinates>");
                        sW.WriteLine("</Point>");
                        sW.WriteLine("</Placemark>");
                        Console.WriteLine("Wrote substation " + Sub.Name + " (" + Sub.LongName + ")");
                        Application.DoEvents();
                    }
                sW.WriteLine("</Folder>");

                //Now, print out the lines
                foreach (String TargetVoltage in KVLevels.Keys)
                {
                    sW.WriteLine("<Folder id=\"Waypoints\">");
                    sW.WriteLine("<name>" + TargetVoltage + "</name>");
                    sW.WriteLine("<visibility>1</visibility>");
                    foreach (CIM_Element Line in Model.Lines.Values)
                        if (Line.ACSegmentStations[0] != Line.ACSegmentStations[1])
                        {
                            String Voltage = Line.ParseSingle("Terminal<TieLine>TieCorridor<DCTie", Line.CIM) == null ? Line.VoltageLevel.Name : "DC Tie";
                            FoundSub1 = Repository.Db.Substations[Line.ACSegmentStations[0].rdfID];
                            FoundSub2 = Repository.Db.Substations[Line.ACSegmentStations[1].rdfID];
                            if (String.Equals(Voltage, TargetVoltage, StringComparison.CurrentCultureIgnoreCase))
                            {
                                sW.WriteLine("<Placemark>");
                                sW.WriteLine("<name>" + Line.LineName + " (" +  MM_Type_Converter.TitleCase(Line.ACSegmentStations[0].LongName) + " to " +  MM_Type_Converter.TitleCase(Line.ACSegmentStations[1].LongName) + ")</name>");
                                sW.WriteLine("<description>Voltage: " + TargetVoltage + "<br/>Estimated Distance: " + EstimateDistance(FoundSub1, FoundSub2).ToString("0.00") + " miles<br/>rdf:ID:" + Line.rdfID.ToString() + "</description>");
                                sW.WriteLine("<styleUrl>#" + TargetVoltage.Replace(' ', '_') + "</styleUrl>");
                                sW.WriteLine("<LineString>");
                                sW.WriteLine("<tessellate>1</tessellate>");
                                sW.WriteLine("<coordinates>");
                                sW.WriteLine(FoundSub1.Longitude.ToString() + "," + FoundSub1.Latitude.ToString() + ",0 " + FoundSub2.Longitude.ToString() + "," + FoundSub2.Latitude.ToString() + ",0");
                                sW.WriteLine("</coordinates>");
                                sW.WriteLine("</LineString>");
                                sW.WriteLine("</Placemark>");
                                Console.WriteLine("Wrote line " + Line.LineName);

                            }
                        }
                    sW.WriteLine("</Folder>");
                }
                sW.WriteLine("</Document>");
                sW.WriteLine("</kml>");
            }
        }

        /// <summary>
        /// Write out an Excel spreadsheet containing substations and lines
        /// </summary>
        /// <param name="TargetFileName"></param>
        /// <param name="Model"></param>
        /// <param name="Repository"></param>
        public static void WriteSubstationAndLineCoordinates(String TargetFileName,  MM_Database_Model Model, MM_Repository Repository)
        {
            //First, delete the Excel doc if needed, then create and set it up            
            File.Delete(TargetFileName);
            using (OleDbConnection oleExcel = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=\"" + TargetFileName + "\";Extended Properties=Excel 8.0;"))
            {
                oleExcel.Open();
                using (OleDbCommand oCmd = new OleDbCommand("CREATE TABLE [Model-Subs] ([rdf:ID] String,[TEID] Integer, [Name] String, [Long Name] String, [Sub Operator] String, [Voltages] String, [Connected Substations] Memo, [Connected Lines] Memo, [PSSE Bus Numbers] Memo, [PSSE Bus Names] Memo, [Latitude] Double, [Longitude] Double, [Triangulated from] String)", oleExcel))
                    oCmd.ExecuteNonQuery();
                using (OleDbCommand oCmd = new OleDbCommand("CREATE TABLE [Model-Lines] ([rdf:ID] String,[TEID] Integer, [Name] String, [From Station] String, [To Station] String, [Voltage] String, [From rdfID] String, [To rdfID] String, [From Lat] Double, [From Long] Double, [To Lat] Double, [To Long] Double, [Est. Distance (miles)] Double)", oleExcel))
                    oCmd.ExecuteNonQuery();
                                
                //Now, add in all our substations and lines
                MM_Substation FoundSub1, FoundSub2;
                foreach (CIM_Element Sub in Model.Substations.Values)
                    if (bool.Parse(Sub["etx:Substation.forNetwork"]) && bool.Parse(Sub["etx:Substation.forScada"]) && Repository.Db.Substations.TryGetValue(Sub.rdfID, out FoundSub1))
                        InsertSubstationRow(oleExcel, Sub, FoundSub1);
                foreach (CIM_Element Line in Model.Lines.Values)
                    if (Line.ACSegmentStations[0] != Line.ACSegmentStations[1] && Repository.Db.Substations.TryGetValue(Line.ACSegmentStations[0].rdfID, out FoundSub1) && Repository.Db.Substations.TryGetValue(Line.ACSegmentStations[1].rdfID, out FoundSub2))
                        InsertLineRow(oleExcel, Line, FoundSub1, FoundSub2);
            }
        }


        /// <summary>
        /// Insert a substation element into the display
        /// </summary>
        /// <param name="oleExcel"></param>
        /// <param name="cSub"></param>
        /// <param name="mSub"></param>
        private static void InsertSubstationRow(OleDbConnection oleExcel, CIM_Element cSub, MM_Substation mSub)
        {
            Dictionary<String, Object> OutObjects = new Dictionary<string, object>();
            OutObjects.Add("rdf:ID", cSub.rdfID);
            OutObjects.Add("TEID", cSub.TEID);
            OutObjects.Add("Name", cSub.Name);
            OutObjects.Add("Long Name", MM_Type_Converter.TitleCase( cSub.LongName));
            OutObjects.Add("Sub Operator", MM_Type_Converter.TitleCase(cSub.Operator.Name));
            OutObjects.Add("Voltages", cSub.VoltageLevels);
            OutObjects.Add("Connected Substations", cSub.ConnectedStationLongNames);
            OutObjects.Add("Connected Lines", cSub.ConnectedLineLongNames);                        
            OutObjects.Add("PSSE Bus Numbers", cSub.PSSEBusNumbers);
            OutObjects.Add("PSSE Bus Names", cSub.PSSEBusNames);
            OutObjects.Add("Latitude", mSub.Latitude);
            OutObjects.Add("Longitude", mSub.Longitude);
            StringBuilder sB2 = new StringBuilder();
            if (mSub.TriangulatedAgainst != null)
                foreach (UInt64 SubTEID in mSub.TriangulatedAgainst)
                    sB2.Append((sB2.Length == 0 ? "" : ",") + MM_Type_Converter.TitleCase(cSub.CIM.FromTEID(SubTEID).LongName));
            OutObjects.Add("Triangulated from", sB2.ToString());
            AddRow(oleExcel, OutObjects, "Model-Subs");
            Console.WriteLine("Inserted substation " + cSub.Name + " (" + cSub.LongName + ")");
            Application.DoEvents();
        }

        /// <summary>
        /// Insert a line row into the display
        /// </summary>
        /// <param name="oleExcel"></param>
        /// <param name="Line"></param>
        /// <param name="Sub1"></param>
        /// <param name="Sub2"></param>
        private static void InsertLineRow(OleDbConnection oleExcel, CIM_Element Line, MM_Substation Sub1, MM_Substation Sub2)
        {
           
            Dictionary<String, Object> OutObjects = new Dictionary<string, object>();
            OutObjects.Add("rdf:ID", Line.rdfID);
            OutObjects.Add("TEID", Line.TEID);
            OutObjects.Add("Name", Line.LineName);
            OutObjects.Add("From Station", MM_Type_Converter.TitleCase(Line.ACSegmentStations[0].LongName));
            OutObjects.Add("To Station", MM_Type_Converter.TitleCase(Line.ACSegmentStations[1].LongName));
            CIM_Element DCTie = Line.ParseSingle("Terminal<TieLine>TieCorridor<DCTie",Line.CIM);
            if (DCTie != null)            
                OutObjects.Add("Voltage", "DC Tie");
            else
                OutObjects.Add("Voltage", Line.VoltageLevel.Name);
            OutObjects.Add("From rdfID", Line.ACSegmentStations[0].rdfID.ToString());
            OutObjects.Add("To rdfID", Line.ACSegmentStations[1].rdfID.ToString());
            OutObjects.Add("From Lat", Sub1.Latitude);
            OutObjects.Add("From Long", Sub1.Longitude);
            OutObjects.Add("To Lat", Sub2.Latitude);
            OutObjects.Add("To Long", Sub2.Longitude);
            OutObjects.Add("Est# Distance (miles)", EstimateDistance(Sub1, Sub2)); 
            AddRow(oleExcel, OutObjects, "Model-Lines");
            Console.WriteLine("Inserted line " + Line.LineName);
            Application.DoEvents();
        }

        /// <summary>
        /// Estimate the distance between two substations
        /// </summary>
        /// <param name="Sub1"></param>
        /// <param name="Sub2"></param>
        /// <returns></returns>
        public static double EstimateDistance(MM_Substation Sub1, MM_Substation Sub2)
        {
            return Math.Acos(Math.Cos(Radians(90 - Sub1.Latitude)) * Math.Cos(Radians(90 - Sub2.Latitude)) + Math.Sin(Radians(90 - Sub1.Latitude)) * Math.Sin(Radians(90 - Sub2.Latitude)) * Math.Cos(Radians(Sub1.Longitude - Sub2.Longitude))) * 3958.756;
        }

        /// <summary>
        /// Estimate the distance between two substations
        /// </summary>
        /// <param name="Line"></param>
        /// <returns></returns>
        public static double EstimateDistance(MM_Line Line)
        {
            double Distance = 0;
            for (int a = 1; a < Line.Coordinates.Length; a++)            
                Distance +=  Math.Abs(Math.Acos(Math.Cos(Radians(90 - Line.Coordinates[a-1].Y)) * Math.Cos(Radians(90 - Line.Coordinates[a].Y)) + Math.Sin(Radians(90 - Line.Coordinates[a-1].Y)) * Math.Sin(Radians(90 - Line.Coordinates[a].Y)) * Math.Cos(Radians(Line.Coordinates[a-1].X - Line.Coordinates[a].X))) * 3958.756);
            return Distance;            
        }


        /// <summary>
        /// Add in a data row to an Excel spreadsheet
        /// </summary>
        /// <param name="oleExcel"></param>
        /// <param name="OutObjects"></param>
        /// <param name="Table"></param>
        public static void AddRow(OleDbConnection oleExcel, Dictionary<String, Object> OutObjects, String Table)
        {
            StringBuilder sB1 = new StringBuilder();
            StringBuilder sB2 = new StringBuilder();
            using (OleDbCommand oCmd = new OleDbCommand())
            {
                oCmd.Connection = oleExcel;
                int CurVal = 0;
                foreach (KeyValuePair<String, Object> kvp in OutObjects)
                    if (CurVal == 0)
                    {
                        sB1.Append("INSERT INTO " + Table.Replace('-','_') + " ([" + kvp.Key + "]");
                        sB2.Append(") VALUES (:" + CurVal.ToString());
                        oCmd.Parameters.AddWithValue(CurVal.ToString(), kvp.Value);
                        CurVal++;
                    }
                    else
                    {
                        sB1.Append(", [" + kvp.Key + "]");
                        sB2.Append(", :" + CurVal.ToString());
                        oCmd.Parameters.AddWithValue(CurVal.ToString(), kvp.Value);
                        CurVal++;
                    }
                oCmd.CommandText = sB1.ToString() + sB2.ToString() + ")";
                oCmd.ExecuteNonQuery();
                Application.DoEvents();
            }
        }

        /// <summary>
        /// Return a degrees to radians conversion
        /// </summary>
        /// <param name="InNumber"></param>
        /// <returns></returns>
        private static double Radians(double InNumber)
        {
            return InNumber * Math.PI / 180.0;
        }

        

        /// <summary>
        /// Update a substation coordinate based on triangulation
        /// </summary>
        /// <param name="mSub">The MM substation</param>
        /// <param name="cSub">The Substation CIM element</param>
        /// <param name="Iteration">The current iteration</param>
        /// <param name="sW">The log for our processing</param>
        /// <param name="Repository"></param>
        /// <returns>True if more than one point was found for triangulation</returns>
        public static bool UpdateCoordinates(MM_Substation mSub, CIM_Element cSub, int Iteration, TextWriter sW, MM_Repository Repository)
        {
            PointD NewPoint = new PointD();
            int NumPoints = 0;
            MM_Substation mOtherSub;
            StringBuilder SubNames = new StringBuilder();
            List<UInt64> SubTEIDs = new List<ulong>();
            foreach (CIM_Element OtherSub in cSub.ConnectedStations)
                if (Repository.Db.Substations.TryGetValue(OtherSub.rdfID, out mOtherSub) && !mOtherSub.LatLong.IsNan && !mOtherSub.LatLong.IsEmpty)
                {
                    NewPoint.X += mOtherSub.LatLong.X;
                    NewPoint.Y += mOtherSub.LatLong.Y;
                    NumPoints++;
                    SubNames.Append((SubNames.Length == 0 ? "" : ", ") + MM_Type_Converter.TitleCase(OtherSub.Name) + " (" + MM_Type_Converter.TitleCase(OtherSub.LongName) + ")");
                    SubTEIDs.Add(Convert.ToUInt64(OtherSub.TEID));
                }

            //If we have one point, put in a warning
            if (NumPoints == 0)
                WriteMessage(sW, "({0}) Error:  no lat/long points were detected for {1} ({2})", Iteration, cSub.Name, cSub.LongName);
            else if (NumPoints == 1)
                WriteMessage(sW, "({0}) Warning: only one lat/long point was detected for {1} ({2})", Iteration, cSub.Name, cSub.LongName);

            if (NumPoints > 0)
            {
                mSub.LatLong = new PointD(NewPoint.X / (double)NumPoints, NewPoint.Y / (double)NumPoints);
                //If we have only one point, shift the longitude by one mile
                if (NumPoints == 1)
                    mSub.Longitude += 1.60934 / (111.111 * Math.Cos(mSub.LatLong.Y));
                mSub.Repositioned = true;
                mSub.ValidatedBy = Repository.CurrentUser;
                mSub.ValidatedOn = DateTime.Now;
                mSub.TriangulatedAgainst = SubTEIDs.ToArray();
                mSub.FindBoundary(Repository);
                mSub.UpdateRdb(Repository);
                WriteMessage(sW, "({0}) Updated lat/lng for {1}: {2}, {3} from {4} substations: {5}", Iteration, MM_Type_Converter.TitleCase( cSub.Name) + " (" + MM_Type_Converter.TitleCase(cSub.LongName) + ")", mSub.LatLong.X, mSub.LatLong.Y, NumPoints, SubNames.ToString());
                if (mSub.Boundary == Repository.Db.StateBoundary)
                    WriteMessage(sW, "({0}) ERROR: Substation {1} ({2}) is outside the state", Iteration, cSub.Name, cSub.LongName);

            }
            return NumPoints > 1;
        }


        /// <summary>
        /// Update one-line orientations
        /// </summary>
        /// <param name="Repository"></param>
        /// <param name="Model"></param>
        [Description("Update one-line orientations"), RequireModel(true), RequireModelOpened(false), System.Runtime.InteropServices.ComVisible(true)]
        public static void UpdateOneLineOrientations(MM_Repository Repository, MM_Database_Model Model)
        {
            //Go through all one-lines for our current model and update to ensure appropriate sizings
            SortedDictionary<String, SortedDictionary<String, int>> UpdatedOneLines = new SortedDictionary<string, SortedDictionary<string, int>>();
            
            int OneLineCount = 0;
            using (DbTransaction dTrans = Repository.Db.BeginTransaction())
            {
                using (DbCommand dCmd = Repository.Db.CreateCommand(dTrans, "SELECT ROWID, DOCUMENT FROM MM_DATABASE_ONELINE WHERE MODELS LIKE :0", "%," + Model.ID.ToString() + ",%"))
                using (DbDataReader dRd = dCmd.ExecuteReader())
                    while (dRd.Read())
                    {
                        XmlDocument xDoc = new XmlDocument();
                        xDoc.LoadXml(dRd["DOCUMENT"].ToString().TrimStart((Char)191));
                        String SubName = xDoc.DocumentElement.Attributes[xDoc.DocumentElement.HasAttribute("BaseElement.Name") ? "BaseElement.Name" : "BaseElement.DisplayName"].Value;
                        Console.Write("Looking for changes in " + SubName + ": ");
                        bool FoundChange = false;

                        //Now, update any elements to appropriate values
                        foreach (XmlElement xElem in xDoc.DocumentElement["Elements"].ChildNodes)
                            ValidateOneLineElementOrientation(SubName, xElem, UpdatedOneLines, ref FoundChange, Model);
                        foreach (XmlElement xElem in xDoc.DocumentElement["Nodes"].ChildNodes)
                        {
                            ValidateOneLineElementOrientation(SubName, xElem, UpdatedOneLines, ref FoundChange, Model);
                            foreach (XmlElement xChild in xElem.ChildNodes)
                                ValidateOneLineElementOrientation(SubName, xChild, UpdatedOneLines, ref FoundChange,Model);
                        }

                        if (FoundChange)
                        {
                            //Back up our old one-line
                            File.WriteAllText("OneLineBackup-" + Model.FullClass + "-" + SubName + ".xml", dRd["DOCUMENT"].ToString());

                            //Update our XML
                            using (StringWriter sW = new StringWriter())
                            {

                                XmlWriterSettings xSet = new XmlWriterSettings();
                                xSet.Encoding = new UTF8Encoding(false);
                                xSet.Indent = true;
                                xSet.OmitXmlDeclaration = false;
                                using (XmlWriter xW = XmlTextWriter.Create(sW, xSet))
                                    xDoc.WriteContentTo(xW);
                                sW.Flush();
                                using (DbCommand dCmd2 = Repository.Db.CreateCommand(dTrans, "UPDATE MM_DATABASE_ONELINE SET DOCUMENT=:0 WHERE ROWID=:1", sW.ToString(), dRd["ROWID"].ToString()))
                                    OneLineCount += dCmd2.ExecuteNonQuery();
                            }
                            StringBuilder sB2 = new StringBuilder();
                            foreach (KeyValuePair<String, int> kvp in UpdatedOneLines[SubName])
                                sB2.Append((sB2.Length==0?"":", ") + kvp.Key + "(" + kvp.Value.ToString("#,##0") + ")");
                            Console.WriteLine("Found changes: " + sB2.ToString());                        
                        }
                        else
                            Console.WriteLine("No changes.");
                        
                    }
                //Now, write out our log
                SortedDictionary<String, int[]> UpdatedTypes = new SortedDictionary<string, int[]>();

                using (StreamWriter sW = new StreamWriter("OneLineUpdate-" + Model.FullClass + ".log", false, new UTF8Encoding(false)))
                    foreach (KeyValuePair<String, SortedDictionary<String, int>> kvp in UpdatedOneLines)
                    {
                        int[] FoundInt;
                        bool WroteHeader = false;
                        foreach (KeyValuePair<String, int> kvp2 in kvp.Value)
                        {
                            //Update our tally count
                            if (!UpdatedTypes.TryGetValue(kvp2.Key, out FoundInt))
                                UpdatedTypes.Add(kvp2.Key, FoundInt = new int[] { 0, 0 });
                            FoundInt[0]++;
                            FoundInt[1] += kvp2.Value;

                            //Write out our elements
                            if (!WroteHeader)
                                sW.Write(kvp.Key + ": " + kvp2.Key + "(" + kvp2.Value.ToString() + ")", WroteHeader = true);
                            else
                                sW.Write(", " + kvp2.Key + "(" + kvp2.Value.ToString() + ")");
                        }
                        sW.WriteLine();
                    }


                StringBuilder sB = new StringBuilder();
                sB.AppendLine("There are " + OneLineCount.ToString("#,##0") + " updated one-lines. Would you like to commit changes?");
                sB.AppendLine();
                sB.AppendLine("Updated types:");
                foreach (KeyValuePair<String, int[]> kvp in UpdatedTypes)
                    sB.AppendLine(kvp.Key + " (" + kvp.Value[0].ToString("#,##0") + " in subs, " + kvp.Value[1].ToString("#,##0") + " total)");

                if (MessageBox.Show(sB.ToString(), Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                    dTrans.Commit();
                else
                    dTrans.Rollback();
            }
        }

        /// <summary>
        /// Validate a one-line element for orientation
        /// </summary>
        /// <param name="SubName"></param>
        /// <param name="xElem"></param>
        /// <param name="UpdatedOneLines"></param>
        /// <param name="FoundChange"></param>
        /// <param name="Model"></param>
        private static void ValidateOneLineElementOrientation(String SubName, XmlElement xElem, SortedDictionary<string,SortedDictionary<string,int>> UpdatedOneLines, ref bool FoundChange, MM_Database_Model Model)
        {
            if (!xElem.HasAttribute("Orientation"))
                return;
            MM_OneLine_Element.enumElemTypes ElemType = (MM_OneLine_Element.enumElemTypes)Enum.Parse(typeof(MM_OneLine_Element.enumElemTypes), xElem.Name);
            MM_OneLine_Element.enumOrientations Orientation = (MM_OneLine_Element.enumOrientations)Enum.Parse(typeof(MM_OneLine_Element.enumOrientations), xElem.Attributes["Orientation"].Value);
            MM_OneLine_Element.enumOrientations[] AllowedOrientations;

            if (ElemType == MM_OneLine_Element.enumElemTypes.Capacitor || ElemType == MM_OneLine_Element.enumElemTypes.Reactor || ElemType == MM_OneLine_Element.enumElemTypes.Resistor || ElemType == MM_OneLine_Element.enumElemTypes.ResistorCapacitor || ElemType == MM_OneLine_Element.enumElemTypes.ResistorReactor)
                AllowedOrientations = MM_OneLine_Element.AllowedOrientations(ElemType, Model.FromRdfID(xElem.Attributes["rdfID"].Value).TypeName == "cim:ShuntCompensator");
            else
                AllowedOrientations = MM_OneLine_Element.AllowedOrientations(ElemType, true);        
            
            
            if (!AllowedOrientations.Contains(Orientation))
            {
                FoundChange = true;

                //Update our list of changed elements
                SortedDictionary<String, int> FoundTypes;
                if (!UpdatedOneLines.TryGetValue(SubName, out FoundTypes))
                    UpdatedOneLines.Add(SubName, FoundTypes = new SortedDictionary<string, int>());

                if (!FoundTypes.ContainsKey(xElem.Name))
                    FoundTypes.Add(xElem.Name, 1);
                else
                    FoundTypes[xElem.Name]++;

                //Now, update our element
                Rectangle FoundBounds = MM_Serializable<Rectangle>.ConvertObject(xElem.Attributes["Bounds"].Value, null);
                Size FoundSize = FoundBounds.Size;
                if (ElemType == MM_OneLine_Element.enumElemTypes.Node)
                    if (FoundSize.Width > FoundSize.Height)
                        xElem.Attributes["Orientation"].Value = "Horizontal";
                    else if (FoundSize.Height > FoundSize.Width)
                        xElem.Attributes["Orientation"].Value = "Vertical";
                    else
                        xElem.Attributes["Orientation"].Value = "Unknown";
                else if (ElemType == MM_OneLine_Element.enumElemTypes.EndCap)
                    xElem.Attributes["Orientation"].Value = "Unknown";
                else if (ElemType == MM_OneLine_Element.enumElemTypes.Transformer || ElemType == MM_OneLine_Element.enumElemTypes.Switch || ElemType == MM_OneLine_Element.enumElemTypes.Breaker)
                    if (Orientation == MM_OneLine_Element.enumOrientations.Left || Orientation == MM_OneLine_Element.enumOrientations.Right)
                        xElem.Attributes["Orientation"].Value = "Horizontal";
                    else if (Orientation == MM_OneLine_Element.enumOrientations.Up || Orientation == MM_OneLine_Element.enumOrientations.Down)
                        xElem.Attributes["Orientation"].Value = "Vertical";
                    else
                    { }
                else if (ElemType == MM_OneLine_Element.enumElemTypes.Descriptor || ElemType == MM_OneLine_Element.enumElemTypes.SecondaryDescriptor)
                    xElem.Attributes["Orientation"].Value = "Unknown";
                else if (AllowedOrientations.Contains(MM_OneLine_Element.enumOrientations.Horizontal) && (Orientation == MM_OneLine_Element.enumOrientations.Left || Orientation == MM_OneLine_Element.enumOrientations.Right))
                    xElem.Attributes["Orientation"].Value = "Horizontal";
                else if (AllowedOrientations.Contains(MM_OneLine_Element.enumOrientations.Vertical) && (Orientation == MM_OneLine_Element.enumOrientations.Up || Orientation == MM_OneLine_Element.enumOrientations.Down))
                    xElem.Attributes["Orientation"].Value = "Vertical";
                else if (Orientation == MM_OneLine_Element.enumOrientations.Horizontal || Orientation == MM_OneLine_Element.enumOrientations.Vertical)
                {
                    XmlElement xNode = (XmlElement)xElem.OwnerDocument.SelectSingleNode("/One_Line/Nodes/Node/*[@TEID='" + xElem.Attributes["BaseElement.TEID"].Value + "']");
                    if (xNode == null)
                        xNode = (XmlElement)xElem.OwnerDocument.SelectSingleNode("/One_Line/Nodes/Node/*[@rdfID='" + xElem.Attributes["rdfID"].Value + "']");

                    XmlNodeList xNodePokes = xNode.SelectNodes("PokePoint");
                    Rectangle NodeRect = xNodePokes.Count == 0 ? MM_Serializable<Rectangle>.ConvertObject(xNode.ParentNode.Attributes["Bounds"].Value, null) : MM_Serializable<Rectangle>.ConvertObject(xNodePokes[xNodePokes.Count - 1].Attributes["Bounds"].Value, null);
                    MM_OneLine_Element.enumOrientations NewOrientation = MM_OneLine_Validation.DetermineOrientation(FoundBounds, NodeRect);
                    xElem.Attributes["Orientation"].Value = NewOrientation.ToString();
                    Console.WriteLine("Updated orientation for " + xElem.Name + " from " + Orientation.ToString() + " to " + NewOrientation.ToString());
                }
                else
                {
                }
            }
        }




        /// <summary>
        /// Undo the validation of a model
        /// </summary>
        /// <param name="Repository"></param>
        /// <param name="Model"></param>
        [Description("Roll back model one-line validation"), RequireModel(true), RequireModelOpened(true)]
        public static void UndoModelValidation(MM_Repository Repository, MM_Database_Model Model)
        {
            //First, clear all assignments of any form associated with the model
            using (DbTransaction dbTrans = Repository.Db.BeginTransaction())
            {
                int DeletedAssignments = 0, RemovedOneLines = 0, UpdatedOneLines = 0, ModelSubstations = 0;

                //Tally the number of substations
                foreach (CIM_Element Sub in Model.Substations.Values)
                    if (bool.Parse(Sub["etx:Substation.forNetwork"]) && bool.Parse(Sub["etx:Substation.forScada"]))
                        ModelSubstations++;

                //Update all one-lines that have been assignmed to this model
                Dictionary<string, string> RowIDs = new Dictionary<string, string>();
                List<String> ToDelete = new List<string>();
                using (DbCommand dCmd = Repository.Db.CreateCommand(dbTrans, "SELECT ROWID, MODELS FROM MM_DATABASE_ONELINE WHERE MODELS LIKE :0", "%," + Model.ID.ToString() + ",%"))
                using (DbDataReader dRd = dCmd.ExecuteReader())
                    while (dRd.Read())
                        if (dRd["MODELS"].ToString()==","+Model.ID.ToString() + ",")
                            ToDelete.Add(dRd["ROWID"].ToString());
                else
                        RowIDs.Add(dRd["ROWID"].ToString(), dRd["MODELS"].ToString().Replace("," + Model.ID.ToString() + ",", ","));


                
                //Delete all assignments to this model
                using (DbCommand dCmd = Repository.Db.CreateCommand(dbTrans, "DELETE FROM MM_DATABASE_ASSIGNMENT WHERE MODEL=:0", Model.ID))
                    DeletedAssignments = dCmd.ExecuteNonQuery();

                //Delete all one-lines which are just for this model
                foreach (String RowID in ToDelete)
                using (DbCommand dCmd = Repository.Db.CreateCommand(dbTrans, "DELETE FROM MM_DATABASE_ONELINE WHERE ROWID=:0", RowID))
                    RemovedOneLines += dCmd.ExecuteNonQuery();

                foreach (KeyValuePair<String, string> kvp in RowIDs)
                    using (DbCommand dCmd = Repository.Db.CreateCommand(dbTrans, "UPDATE MM_DATABASE_ONELINE SET MODELS=:0 WHERE ROWID=:1", kvp.Value, kvp.Key))
                        UpdatedOneLines += dCmd.ExecuteNonQuery();

                StringBuilder sB = new StringBuilder();
                sB.AppendLine("Would you like to update the database to remove this model's one-lines?");
                sB.AppendLine();
                sB.AppendLine("Substations: " + ModelSubstations.ToString("#,##0"));
                sB.AppendLine("Assignments to clear: " + DeletedAssignments.ToString("#,##0"));                
                sB.AppendLine("Onelines to delete: " + RemovedOneLines.ToString("#,##0"));
                sB.AppendLine("Onelines to update: " + UpdatedOneLines.ToString("#,##0"));
                sB.AppendLine("Total one-lines to change: " + (RemovedOneLines + UpdatedOneLines).ToString("#,##0"));
                if (MessageBox.Show(sB.ToString(), Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                    dbTrans.Commit();
                else
                    dbTrans.Rollback();
            }



        }

        /// <summary>
        /// Write a message out to our log file and our console
        /// </summary>
        /// <param name="sW"></param>
        /// <param name="format"></param>
        /// <param name="args"></param>
        private static void WriteMessage(TextWriter sW, String format, params object[] args)
        {
            string OutStr = string.Format(format, args);
            sW.WriteLine(OutStr);
            Console.WriteLine(OutStr);
        }
    }
}